(import
 (except (rnrs base) let-values map error)
 (only (guile)
       lambda* λ
       current-output-port)
 (fileio)
 (list-helpers)
 (srfi srfi-1)
 ;; let-values
 (srfi srfi-11)
 ;; hash tables
 (srfi srfi-69)
 (ice-9 pretty-print))


(define array-len-in-dim
  (λ (arr dim)
    (let* ([shape (array-shape arr)]
           [dim-min-max (list-ref shape dim)])
      (+ (- (second dim-min-max)
            (first dim-min-max))
         1))))


(define distances
  (list->array
   2
   '(((-2 . -2) (-2 . -1) (-2 . 0) (-2 . 1) (-2 . 2))
     ((-1 . -2) (-1 . -1) (-1 . 0) (-1 . 1) (-1 . 2))
     (( 0 . -2) ( 0 . -1) ( 0 . 0) ( 0 . 1) ( 0 . 2))
     (( 1 . -2) ( 1 . -1) ( 1 . 0) ( 1 . 1) ( 1 . 2))
     (( 2 . -2) ( 2 . -1) ( 2 . 0) ( 2 . 1) ( 2 . 2)))))


(define tail-moves
  (list->array
   2
   '(((-1 . -1) (-1 . -1) (-1 . 0) (-1 . 1) (-1 . 1))
     ((-1 . -1) ( 0 .  0) ( 0 . 0) ( 0 . 0) (-1 . 1))
     (( 0 . -1) ( 0 .  0) ( 0 . 0) ( 0 . 0) ( 0 . 1))
     (( 1 . -1) ( 0 .  0) ( 0 . 0) ( 0 . 0) ( 1 . 1))
     (( 1 . -1) ( 1 . -1) ( 1 . 0) ( 1 . 1) ( 1 . 1)))))


(define move-str-move-table
  (alist->hash-table
   '(("U" . (-1 . 0))
     ("D" . (1 . 0))
     ("L" . (0 . -1))
     ("R" . (0 . 1)))))


(define arrays->hash-table
  (lambda* (keys-arr vals-arrs #:optional (equal-func equal?))
    (let ([rows (array-len-in-dim keys-arr 0)]
          [cols (array-len-in-dim keys-arr 1)]
          [table (make-hash-table equal-func)])
      (let iter-rows ([row-ind 0])
        (let iter-cols ([col-ind 0])
          (cond
           [(< row-ind rows)
            (cond
             [(< col-ind cols)
              (hash-table-set! table
                               (array-ref keys-arr row-ind col-ind)
                               (array-ref vals-arrs row-ind col-ind))
              (iter-cols (+ col-ind 1))]
             [else (iter-rows (+ row-ind 1))])]
           [else table]))))))


(define distances-tail-move-table
  (arrays->hash-table distances tail-moves))


(define update-coords
  (λ (coords diff-coords)
    (cons (+ (car coords)
             (car diff-coords))
          (+ (cdr coords)
             (cdr diff-coords)))))


(define coords-diff
  (λ (coords1 coords2)
    (cons (- (car coords1)
             (car coords2))
          (- (cdr coords1)
             (cdr coords2)))))


(define lines (get-lines-from-file "input"))


(define make-list
  (λ (val count)
    (cond
     [(> count 0)
      (cons val (make-list val (- count 1)))]
     [else '()])))


(define line->moves
  (λ (line)
    (let* ([parts (string-split line #\space)]
           [direction (first parts)]
           [times (string->number (second parts))])
      (make-list (hash-table-ref move-str-move-table direction)
                 times))))


(define moves (apply append (map line->moves lines)))


(define simulate-move
  (λ (state move visited-table)
    (let iter-knots ([state-ind 0] [move° move])
      ;; (simple-format (current-output-port) "state: ~a\n" state)
      ;; (simple-format (current-output-port)
      ;;                "updating knot: ~a at coordinates: ~a with move: ~a\n"
      ;;                state-ind
      ;;                (vector-ref state state-ind)
      ;;                move°)
      (cond
       [(< state-ind (- (vector-length state) 1))
        (let* ([knot (vector-ref state state-ind)]
               [updated-knot (update-coords knot move°)])
          (vector-set! state state-ind updated-knot)
          (let* ([next-knot (vector-ref state (+ state-ind 1))]
                 [next-move (hash-table-ref distances-tail-move-table
                                            (coords-diff updated-knot
                                                         next-knot))])
            (iter-knots (+ state-ind 1) next-move)))]
       ;; If at last knot, move it and store its
       ;; coordinates.
       [else
        (let* ([knot (vector-ref state state-ind)]
               [updated-knot (update-coords knot move°)])
          ;; Update the knot's coords.
          (vector-set! state state-ind updated-knot)
          ;; Mark the updated knot coords as visited.
          (hash-table-update! visited-table
                              updated-knot
                              (λ (old-count) (+ old-count 1))
                              (λ () 1))
          (values state visited-table))]))))


(define tail-visited-table
  (let* ([init-coords (cons 0 0)]
         [initial-state (make-vector 10 init-coords)])
    (let iter ([state initial-state]
               [moves° moves]
               [visited-table
                ;; Do not forget, that the tail is at the initial
                ;; coordinates initially.
                (alist->hash-table `((,init-coords . 1)))])
      (cond
       [(null? moves°) visited-table]
       [else
        (let-values ([(updated-state updated-visited-table)
                      (simulate-move state
                                     (first moves°)
                                     visited-table)])
          (iter updated-state
                (drop moves° 1)
                updated-visited-table))]))))


(define sum
  (λ (lst)
    (apply + lst)))

(simple-format (current-output-port)
               "~a\n"
               (sum
                (map (λ (val) (if (> val 0) 1 0))
                     (hash-table-values tail-visited-table))))
